package com.alphas.openapigateway;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.serializer.ValueFilter;
import com.alphas.openapiclientsdk.utils.SignUtils;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebExceptionHandler;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;


/**
 * @author alphas
 * @version 1.0
 * @description 全局过滤器
 * @date 2020/8/15 15:47
 */
@Slf4j
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {
    private static final List<String> IP_WHITE_LIST = Arrays.asList("127.0.0.1", "0:0:0:0:0:0:0:1");
    private static final long MAX_TIMESTAMP_DIFF = 5 * 60;
    private final WebExceptionHandler responseStatusExceptionHandler;

    public CustomGlobalFilter(WebExceptionHandler responseStatusExceptionHandler) {
        this.responseStatusExceptionHandler = responseStatusExceptionHandler;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.请求日志
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        log.info("请求唯一标识" + request.getId());
        log.info("请求路径：" + request.getPath().value());
        log.info("请求方法：" + request.getMethod());
        log.info("请求参数：" + request.getQueryParams());
        log.info("请求来源地址：" + request.getRemoteAddress());
        String hostString = request.getLocalAddress().getHostString();
        log.info("请求来源地址：" + hostString);
        log.info("custom global filter");

        //2.响应控制(黑白名单)
        if (!IP_WHITE_LIST.contains(hostString)) {
            return handleNoAuth(response);
        }

        //3.用户鉴权
        HttpHeaders headers = request.getHeaders();
        String accessKey = headers.getFirst("accessKey");
        String nonce = headers.getFirst("nonce");
        String timestamp = headers.getFirst("timestamp");
        String sign = headers.getFirst("sign");
        String body = headers.getFirst("body");
        // 1. 基本参数校验
        if (anyEmpty(accessKey, body, timestamp, nonce, sign)) {
            return handleNoAuth(response);
        }
        //2. 校验accessKey有效性
        if (!"alphas".equals(accessKey)) {
            return handleNoAuth(response);
        }
        //todo 模拟用户鉴权(从数据库读取)
        //此处accesskey应该为从数据库中读取的secretKey
        String severSign = SignUtils.genSign(body, accessKey);
        if (!sign.equals(severSign)) {
            return handleNoAuth(response);
        }
        // 3. 校验时间戳有效性
        long currentTime = System.currentTimeMillis() / 1000;
        long requestTime = Long.parseLong(timestamp);
        if (Math.abs(currentTime - requestTime) > MAX_TIMESTAMP_DIFF) {
            return handleNoAuth(response);
        }
        // 4. 校验nonce唯一性
        if (Long.parseLong(nonce) > 10000L) {
            return handleNoAuth(response);
        }
        // 5. 生成服务端签名
//        Digester sha256 = new Digester(DigestAlgorithm.SHA256);
//        String serverSign = sha256.digestHex(decodedBody + "." + secretKey);
//        System.out.println("这是服务端的签名" + serverSign);
        // 6. 校验签名
//        if (!serverSign.equals(sign)) {
//            throw new RuntimeException("签名验证失败");
//        }
        //4.请求的用户接口是否存在
        //todo从数据库中查询接口是否存在，并校验参数是否正确

        //5.请求转发
        Mono<Void> filter = chain.filter(exchange);

        //6.响应日志
        return handleResponse(exchange, chain);

//        //7.todo调用成功，调用次数加1 invokeCount + 1
//        if (response.getStatusCode() == HttpStatus.OK) {
//
//        } else {
//            return handleInvokeError(response);
//        }

//        //8.调用失败,返回错误码
//
//        return filter;

    }

    @Override
    public int getOrder() {
        return -1;
    }

    public Mono<Void> handleNoAuth(ServerHttpResponse response) {
        response.setStatusCode(HttpStatus.FORBIDDEN);
        return response.setComplete();
    }

    public Mono<Void> handleInvokeError(ServerHttpResponse response) {
        response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
        return response.setComplete();
    }

    private boolean anyEmpty(String... params) {
        return Arrays.stream(params).anyMatch(p -> p == null || p.trim().isEmpty());
    }
    public Mono<Void> handleResponse(ServerWebExchange exchange, GatewayFilterChain chain) {
        try {
            ServerHttpResponse originalResponse = exchange.getResponse();
            DataBufferFactory bufferFactory = originalResponse.bufferFactory();
            HttpStatus statusCode = originalResponse.getStatusCode();
            if (statusCode != HttpStatus.OK) {
                return chain.filter(exchange);//降级处理返回数据
            }
            ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
                @Override
                public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                    if (body instanceof Flux) {
                        Flux<? extends DataBuffer> fluxBody = Flux.from(body);

                        return super.writeWith(fluxBody.buffer().map(dataBuffers -> {

                            // 合并多个流集合，解决返回体分段传输
                            DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                            DataBuffer buff = dataBufferFactory.join(dataBuffers);
                            byte[] content = new byte[buff.readableByteCount()];
                            buff.read(content);
                            DataBufferUtils.release(buff);//释放掉内存

                            //排除Excel导出，不是application/json不打印。若请求是上传图片则在最上面判断。
                            MediaType contentType = originalResponse.getHeaders().getContentType();
                            if (!MediaType.APPLICATION_JSON.isCompatibleWith(contentType)) {
                                return bufferFactory.wrap(content);
                            }

                            // 构建返回日志
                            String joinData = new String(content);
                            String result = modifyBody(joinData);
                            List<Object> rspArgs = new ArrayList<>();
                            rspArgs.add(originalResponse.getStatusCode().value());
                            rspArgs.add(exchange.getRequest().getURI());
                            rspArgs.add(result);
                            log.info("<-- {} {}\n{}", rspArgs.toArray());

                            getDelegate().getHeaders().setContentLength(result.getBytes().length);
                            return bufferFactory.wrap(result.getBytes());
                        }));
                    } else {
                        log.error("<-- {} 响应code异常", getStatusCode());
                    }
                    return super.writeWith(body);
                }
            };
            return chain.filter(exchange.mutate().response(decoratedResponse).build());

        } catch (Exception e) {
            log.error("网关处理响应异常.\n" + e);
            return chain.filter(exchange);
        }
    }
    //返回统一的JSON日期数据 2024-02-23 11:00， null转空字符串
    private String modifyBody(String jsonStr){
        JSONObject json = JSON.parseObject(jsonStr, Feature.AllowISO8601DateFormat);
        JSONObject.DEFFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm";
        return JSONObject.toJSONString(json, (ValueFilter) (object, name, value) -> value == null ? "" : value, SerializerFeature.WriteDateUseDateFormat);
    }

}
